/*!	 trgt_mng.cpp
**	 MNG Target Module
**
**	Copyright (c) 2007 Paul Wise
**	Copyright (c) 2007 Chris Moore
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
** === N O T E S ===========================================================
**
** You will need to read the PNG and MNG specs to understand this code
**
** ========================================================================= */

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <synfig/listimporter.h>
#include <synfig/general.h>

#include "trgt_mng.h"
#include <libmng.h>
#include <ETL/stringf>
#include <cstdio>
#include <algorithm>
#include <functional>
#include <ETL/misc>

#endif

using namespace synfig;
using namespace std;
using namespace etl;

SYNFIG_TARGET_INIT(mng_trgt);
SYNFIG_TARGET_SET_NAME(mng_trgt, "mng");
SYNFIG_TARGET_SET_EXT(mng_trgt, "mng");
SYNFIG_TARGET_SET_VERSION(mng_trgt, "0.1");
SYNFIG_TARGET_SET_CVS_ID(mng_trgt, "$Id$");

static mng_ptr MNG_DECL
mng_alloc_proc(mng_size_t size)
{
    return (mng_ptr)calloc(1, size);
}

static void MNG_DECL
mng_free_proc(mng_ptr ptr, mng_size_t size __attribute__((unused)))
{
    free(ptr);
    return;
}

static mng_bool MNG_DECL
mng_null_proc(mng_handle mng __attribute__((unused)))
{
    return MNG_TRUE;
}

static mng_bool MNG_DECL
mng_write_proc(mng_handle mng, mng_ptr buf, mng_uint32 size, mng_uint32* written)
{
    FILE* file = (FILE*)mng_get_userdata(mng);
    *written = fwrite(buf, 1, size, file);
    return MNG_TRUE;
}

static mng_bool MNG_DECL
mng_error_proc(mng_handle mng __attribute__((unused)), mng_int32 error __attribute__((unused)),
               mng_int8 severity __attribute__((unused)), mng_chunkid chunkname __attribute__((unused)),
               mng_uint32 chunkseq __attribute__((unused)), mng_int32 extra1 __attribute__((unused)),
               mng_int32 extra2 __attribute__((unused)), mng_pchar errortext)
{
    synfig::error("%s:%d mng_trgt: error: %s", __FILE__, __LINE__, errortext);
    return MNG_TRUE;
}

mng_trgt::mng_trgt(const char *Filename, const synfig::TargetParam & /* params */):
    file(NULL),
    w(),
    h(),
    mng(NULL),
    multi_image(),
    ready(false),
    imagecount(),
    filename(Filename),
    buffer(NULL),
    color_buffer(NULL),
    zstream(),
    zbuffer(NULL),
    zbuffer_len(0)
{ }

mng_trgt::~mng_trgt()
{
    synfig::info("mng_trgt: ~mng_trgt");

    if (mng != MNG_NULL) {
        mng_putchunk_mend(mng);

        if (mng_write(mng) != 0) {
            mng_int8 severity;
            mng_chunkid chunkname;
            mng_uint32 chunkseq;
            mng_int32 extra1;
            mng_int32 extra2;
            mng_pchar errortext;
            mng_getlasterror(mng, &severity, &chunkname, &chunkseq, &extra1, &extra2, &errortext);
            synfig::error("mng_trgt: error: couldn't write mng: %s", errortext);
        }

        mng_cleanup(&mng);
    }

    if (file != NULL) {
        fclose(file);
    }

    file = NULL;

    if (buffer != NULL) {
        delete [] buffer;
        buffer = NULL;
    }

    if (color_buffer != NULL) {
        delete [] color_buffer;
        color_buffer = NULL;
    }

    if (zbuffer != NULL) {
        free(zbuffer);
        zbuffer = NULL;
        zbuffer_len = 0;
    }
}

bool
mng_trgt::set_rend_desc(RendDesc *given_desc)
{
    desc = *given_desc;
    imagecount = desc.get_frame_start();

    if (desc.get_frame_end() - desc.get_frame_start() > 0) {
        multi_image = true;
    } else {
        multi_image = false;
    }

    return true;
}

bool
mng_trgt::init(synfig::ProgressCallback * /* cb */)
{

    int frame_rate, num_frames, play_time;
    int num_layers = 1;

    if (multi_image) {
        frame_rate = int(desc.get_frame_rate());
        printf("frame rt %d\n", frame_rate);
        num_frames = desc.get_frame_end() - desc.get_frame_start();
        play_time = num_frames;
    } else {
        frame_rate = 0;
        num_frames = 1;
        play_time = 0;
    }

    time_t t = time(NULL);
    struct tm* gmt = gmtime(&t);
    w = desc.get_w();
    h = desc.get_h();
    file = fopen(filename.c_str(), POPEN_BINARY_WRITE_TYPE);

    if (file == NULL) {
        goto cleanup_on_error;
    }

    mng = mng_initialize((mng_ptr)file, mng_alloc_proc, mng_free_proc, MNG_NULL);

    if (mng == MNG_NULL) {
        goto cleanup_on_error;
    }

    if (mng_setcb_errorproc(mng, mng_error_proc) != 0) {
        goto cleanup_on_error;
    }

    if (mng_setcb_writedata(mng, mng_write_proc) != 0) {
        goto cleanup_on_error;
    }

    if (mng_setcb_openstream(mng, mng_null_proc) != 0) {
        goto cleanup_on_error;
    }

    if (mng_setcb_closestream(mng, mng_null_proc) != 0) {
        goto cleanup_on_error;
    }

    if (mng_create(mng) != 0) {
        goto cleanup_on_error;
    }

    if (mng_putchunk_mhdr(mng, w, h, frame_rate, num_layers, num_frames, play_time, MNG_SIMPLICITY_VALID | MNG_SIMPLICITY_SIMPLEFEATURES) != 0) {
        goto cleanup_on_error;
    }

    if (mng_putchunk_term(mng, MNG_TERMACTION_REPEAT, MNG_ITERACTION_LASTFRAME, 0, 0x7fffffff) != 0) {
        goto cleanup_on_error;
    }

    {
        char title[] = MNG_TEXT_TITLE;

        if (mng_putchunk_text(mng, sizeof(title), title,
                              get_canvas()->get_name().length(), const_cast<char *>(get_canvas()->get_name().c_str())) != 0) {
            goto cleanup_on_error;
        }

        char description[] = MNG_TEXT_DESCRIPTION;

        if (mng_putchunk_text(mng, sizeof(description), description,
                              get_canvas()->get_description().length(), const_cast<char *>(get_canvas()->get_description().c_str())) != 0) {
            goto cleanup_on_error;
        }

        char software[] = MNG_TEXT_SOFTWARE;
        char synfig[] = "SYNFIG";

        if (mng_putchunk_text(mng, sizeof(software), software,
                              sizeof(synfig), synfig) != 0) {
            goto cleanup_on_error;
        }
    }

    if (mng_putchunk_gama(mng, MNG_FALSE, (int)(gamma().get_gamma() * 100000)) != 0) {
        goto cleanup_on_error;
    }

    if (mng_putchunk_phys(mng, MNG_FALSE, round_to_int(desc.get_x_res()), round_to_int(desc.get_y_res()), MNG_UNIT_METER) != 0) {
        goto cleanup_on_error;
    }

    if (mng_putchunk_time(mng, gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday, gmt->tm_hour, gmt->tm_min, gmt->tm_sec) != 0) {
        goto cleanup_on_error;
    }

    buffer = new unsigned char[(4 * w) + 1];

    if (buffer == NULL) {
        goto cleanup_on_error;
    }

    color_buffer = new Color[w];

    if (color_buffer == NULL) {
        goto cleanup_on_error;
    }

    return true;

cleanup_on_error:
    ready = false;

    if (mng != MNG_NULL) {
        mng_int8 severity;
        mng_chunkid chunkname;
        mng_uint32 chunkseq;
        mng_int32 extra1;
        mng_int32 extra2;
        mng_pchar errortext;
        mng_getlasterror(mng, &severity, &chunkname, &chunkseq, &extra1, &extra2, &errortext);
        synfig::error("mng_trgt: libmng: %s", errortext);
        mng_cleanup(&mng);
    }

    if (file && file != stdout) {
        fclose(file);
    }

    file = NULL;

    if (buffer != NULL) {
        delete [] buffer;
        buffer = NULL;
    }

    if (color_buffer != NULL) {
        delete [] color_buffer;
        color_buffer = NULL;
    }

    return false;
}

void
mng_trgt::end_frame()
{

    if (deflate(&zstream, Z_FINISH) != Z_STREAM_END) {
        synfig::error("%s:%d deflate()", __FILE__, __LINE__);
        return;
    }

    if (deflateEnd(&zstream) != Z_OK) {
        synfig::error("%s:%d deflateEnd()", __FILE__, __LINE__);
        return;
    }

    if (mng != MNG_NULL) {
        mng_putchunk_idat(mng, zstream.next_out - zbuffer, zbuffer);
        mng_putchunk_iend(mng);
    }

    imagecount++;
    ready = false;
}

bool
mng_trgt::start_frame(synfig::ProgressCallback *callback __attribute__((unused)))
{

    if (mng == MNG_NULL) {
        synfig::error("%s:%d mng == MNG_NULL", __FILE__, __LINE__);
        return false;
    }

    if (mng_putchunk_ihdr(mng, w, h, MNG_BITDEPTH_8, MNG_COLORTYPE_RGBA, MNG_COMPRESSION_DEFLATE, MNG_FILTER_ADAPTIVE, MNG_INTERLACE_NONE) != 0) {
        synfig::error("%s:%d mng_putchunk_ihdr()", __FILE__, __LINE__);
        return false;
    }

    zstream.zalloc = Z_NULL;
    zstream.zfree = Z_NULL;
    zstream.opaque = Z_NULL;

    if (deflateInit(&zstream, /* Z_BEST_COMPRESSION */ Z_DEFAULT_COMPRESSION) != Z_OK) {
        synfig::error("%s:%d deflateInit()", __FILE__, __LINE__);
        return false;
    }

    if (zbuffer == NULL) {
        zbuffer_len = deflateBound(&zstream, ((4 * w) + 1) * h); // don't forget the 'filter' byte - one per scanline
        zbuffer = (unsigned char*)realloc(zbuffer, zbuffer_len);
    }

    zstream.avail_out = zbuffer_len;
    zstream.next_out = zbuffer;

    ready = true;

    return true;
}

Color*
mng_trgt::start_scanline(int scanline __attribute__((unused)))
{
    return color_buffer;
}

bool
mng_trgt::end_scanline()
{
    if (!file || !ready) {
        synfig::error("%s:%d !file or !ready", __FILE__, __LINE__);
        return false;
    }

    *buffer = MNG_FILTER_NONE;
    convert_color_format(buffer + 1, color_buffer, desc.get_w(), PF_RGB | PF_A, gamma());

    zstream.next_in = buffer;
    zstream.avail_in = (4 * w) + 1;

    if (deflate(&zstream, Z_NO_FLUSH) != Z_OK) {
        synfig::error("%s:%d deflate()", __FILE__, __LINE__);
        return false;
    }

    return true;
}